﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
using Microsoft.Practices.Unity;
using Wyn.PMS.Common.Utils;
using Wyn.PMS.Entity.DataAccess;
using Wyn.PMS.Entity.Entities;
using Wyn.PMS.Entity.IDataAccess;

namespace Wyn.PMS.Common.NightAudit
{
    internal class AccountRemain
    {
        private static readonly IUnityContainer container = UnityContainerService.GetContainer();

        public IList<NaAccountRemain> CalculateRemain(HotelBizDay bizDay, IEnumerable<AccountRoot> accountRoots, IList<Account> notRollAccounts)
        {
            INaAccountRemainRepository accountRemainRepository = container.Resolve<INaAccountRemainRepository>();
            
            IOvertimeDiscountRepository overtimeDiscountRepository = container.Resolve<IOvertimeDiscountRepository>();

            INaHotelRemainRepository hotelRemainRepository = container.Resolve<INaHotelRemainRepository>();

            IPreAuthorizationRepository preAuthRepository = container.Resolve<IPreAuthorizationRepository>();
            
            var transactionOptions = new System.Transactions.TransactionOptions();

            transactionOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted;

            string hotelId = bizDay.HotelID;

            DateTime todayBizday = bizDay.BizDay;

            DateTime yesterBizDay = bizDay.BizDay.AddDays(-1);

            IList<string> orderIds = accountRoots.Select(o => o.AccountRootID).ToList();

            string preAuth_S = EnumHelper.GetCode(PreauthorizationStatus.S);

            Expression<Func<NaAccountRemain, bool>> exp = a => a.HotelID == hotelId && a.BizDay == yesterBizDay;
            
            Expression<Func<OvertimeDiscount, bool>> discountExp = d => d.HotelID == hotelId && d.BizDay == todayBizday;

            IList<NaAccountRemain> yesterRemains =new List<NaAccountRemain>();

            IList<OvertimeDiscount> discounts = new List<OvertimeDiscount>();

            IList<PreAuthorization> preAuths =new List<PreAuthorization>();

            NaHotelRemain yesterHotelRemain = new NaHotelRemain();

            using (var scope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
            {
                yesterRemains = accountRemainRepository.BatchFind(hotelId, exp);

                discounts = overtimeDiscountRepository.BatchFind(hotelId, discountExp);

                preAuths = preAuthRepository.BatchFind(hotelId,p => orderIds.Contains(p.RelateID) && p.Status == preAuth_S);

                yesterHotelRemain = hotelRemainRepository.Find(hotelId, hotelId, todayBizday.AddDays(-1));

                scope.Complete();
            }
            
            IList<NaAccountRemain> accountRemains = new List<NaAccountRemain>();

            NaHotelRemain hotelRemain = new NaHotelRemain {HotelID=hotelId,BizDay=todayBizday,BizDayStart=bizDay.AuditTime,BizDayEnd=DateTime.Now};
           
            foreach(var accountRoot in accountRoots)
            {
                if (accountRoot.Order == null) continue;

                var yesterRemain = yesterRemains.FirstOrDefault(r => r.OrderID == accountRoot.AccountRootID);
                
                var priceDetail = GetPriceDetail(accountRoot,todayBizday,hotelId);

                var discount = discounts.FirstOrDefault(d => d.OrderID == accountRoot.AccountRootID);
                
                var remain = BuildRemain(accountRoot, bizDay, yesterRemain, priceDetail, discount, preAuths, hotelRemain);

                accountRemains.Add(remain);
            }

            hotelRemain = BuildHotelRemain(hotelRemain, notRollAccounts, yesterHotelRemain);

            if (bizDay.Status != EnumHelper.GetCode(NightAuditCode.Status.E)
                || (bizDay.Status == EnumHelper.GetCode(NightAuditCode.Status.E)
                && bizDay.Steping == (int)NightAuditCode.Steping.AccountRemain))
            {
                bizDay.Status = EnumHelper.GetCode(NightAuditCode.Status.I);

                using (TransactionScope scope = new TransactionScope())
                {
                    accountRemainRepository.BatchInsert(hotelId, accountRemains);

                    hotelRemainRepository.Insert(hotelRemain);

                    scope.Complete();
                }
            }

            return accountRemains;
        }

        private NaHotelRemain BuildHotelRemain(NaHotelRemain hotelRemain, IList<Account> notRollAccounts, NaHotelRemain yesterdayRemain)
        {

            hotelRemain.BeginRemain = yesterdayRemain != null
                                          ? yesterdayRemain.BeginRemain + yesterdayRemain.Occur - yesterdayRemain.Receive + Math.Abs(yesterdayRemain.Rebate)
                                          : 0;

            hotelRemain.Occur = notRollAccounts.Where(a => a.LedgerType == AccountType.D).Sum(a => a.Balance);

            hotelRemain.Receive = notRollAccounts.Where(a => a.LedgerType == AccountType.C && a.Balance > 0).Sum(a => a.Balance);

            hotelRemain.Rebate = notRollAccounts.Where(a => a.LedgerType == AccountType.C && a.Balance < 0).Sum(a => a.Balance);

            hotelRemain.Remain = hotelRemain.Receive + hotelRemain.Rebate - hotelRemain.Occur;

            hotelRemain.AllRoomPrice = SumRoomTypePrice(hotelRemain.HotelID, hotelRemain.BizDay);

            return hotelRemain;
        }

        private decimal SumRoomTypePrice(string hotelId, DateTime bizDay)
        {
            INaOperationStatusRepository statusRepository = container.Resolve<INaOperationStatusRepository>();

            DateTime yesterDay = bizDay.AddDays(-1);

            PriceCache pc = PriceCache.GetInstance();

            IList<NaOperationStatus> yesterDayStates = statusRepository.BatchFind(hotelId,s =>s.HotelID == hotelId &&bizDay == yesterDay);
            
            decimal allPrice = 0;

            if (yesterDayStates != null && yesterDayStates.Count > 0)
            {
                foreach (var state in yesterDayStates)
                {
                    var rooms = state.Count - state.OOOCount;

                    decimal price = pc.GetRoomPrice(hotelId, state.RoomTypeID, bizDay);

                    allPrice += rooms * price;
                }
            }
            else
            {
                IRoomTypeRepository rtr = container.Resolve<IRoomTypeRepository>();

                var roomtypes = rtr.BatchFind(hotelId, r => r.HotelID == hotelId && r.IsValid == true);

                foreach (var roomType in roomtypes)
                {
                    var rooms = roomType.TotalRooms;

                    decimal price = pc.GetRoomPrice(hotelId, roomType.RoomTypeID, bizDay);

                    allPrice += rooms * price;
                }
            }

            return allPrice;
        }

        private NaAccountRemain BuildRemain(AccountRoot accountRoot, HotelBizDay bizDay, NaAccountRemain yesterRemain,PriceDetail priceDetail,OvertimeDiscount discount,IList<PreAuthorization> preAuths,NaHotelRemain hotelRemain)
        {
            NaAccountRemain remain = new NaAccountRemain();

            string as_X = EnumHelper.GetCode(AccountStatus.X);
            
            if (accountRoot.Order == null)
            {
                throw new ArgumentNullException(string.Format("非正常的主账单{0}:未找到对应的订单",accountRoot.AccountRootID));
            }

            var order = accountRoot.Order;

            remain.BizDay = bizDay.BizDay;

            remain.OrderID = accountRoot.AccountRootID;

            remain.HotelID = accountRoot.HotelID;

            remain.ResvID = order.ResvID;

            remain.RelateOrderID = order.RelateOrderID;

            remain.GuestSourceType = order.GuestSourceType;
            
            remain.MemberNo   = order.MemberNo;

            remain.IsMarster = order.IsMaster;

            remain.SalerID = order.SalesID;

            remain.GuestName = order.GuestName;

            remain.Country = order.Country;

            //remain.NativePlace = order.OrderOtherInfo.NativePlace;

            remain.CheckInType = order.CheckInType;

            remain.RoomTypeID = order.RoomTypeID;

            remain.RoomNo = order.RoomNo;

            remain.CheckInTime = order.CheckinTime;

            remain.CheckOutTime = order.CheckOutTime??order.DepartureDate;

            if (priceDetail!=null)
            {
                remain.NormalPrice = priceDetail.NormalPrice;

                remain.Price = priceDetail.Price;
            }
            
            remain.ExtraDiscount = discount!=null?discount.Money:0;

            var orderAuths = preAuths.Where(o => o.RelateID == accountRoot.AccountRootID).ToList();

            remain.PreauthSum = orderAuths.Any() ? orderAuths.Sum(o => o.Balance) : 0;

            #region 根据账目计算结余

            string asI = EnumHelper.GetCode(AccountStatus.I);

            if (accountRoot.Account != null && accountRoot.Account.Any())
            {
                remain.AllCreditSum = accountRoot.Account.Where(a => a.Status!=as_X && !a.IsStrike && a.LedgerType == AccountType.C).Sum(a => a.Balance);

                remain.AllDebitSum = accountRoot.Account.Where(a => a.Status != as_X && !a.IsStrike && a.LedgerType == AccountType.D).Sum(a => a.Balance);

                remain.AllRemain = remain.AllCreditSum - remain.AllDebitSum;

                hotelRemain.RMBRemain += accountRoot.Account
                    .Where(a => a.Status == asI
                                && !a.IsStrike
                                && (a.LedgerCode == LedgerCode.人民币押金 || a.LedgerCode == LedgerCode.人民币))
                    .Sum(a => a.Balance);

                hotelRemain.HKRemain += accountRoot.Account
                    .Where(a => a.Status == asI
                                && !a.IsStrike
                                && a.LedgerCode == LedgerCode.港币押金 || a.LedgerCode == LedgerCode.港币)
                    .Sum(a => a.Balance);

                hotelRemain.Remain += remain.AllRemain;

                //有效细账统计
                var validAccounts = accountRoot.Account.Where(a => a.BizDay <= bizDay.BizDay && !a.IsRentRoll && !a.IsStrike && a.Status != as_X ).ToList();

                if (validAccounts.Any())
                {
                    remain.ExtraPassNightPrice = validAccounts.Where(a => a.LedgerCode == LedgerCode.全日租).Sum(a => a.Balance);

                    remain.ExtraHalfDayPrice = validAccounts.Where(a => a.LedgerCode == LedgerCode.半日租).Sum(a => a.Balance);

                    remain.ExtraBedFee = validAccounts.Where(a => a.LedgerCode == LedgerCode.加床).Sum(a => a.Balance);

                    remain.RoomFeeSum = validAccounts.Where(a => a.LedgerParentCode == LedgerCode.客房收入).Sum(a => a.Balance);

                    remain.EntertainmentFeeSum = validAccounts.Where(a => a.LedgerParentCode == LedgerCode.其他代收).Sum(a => a.Balance);

                    remain.TelephoneFeeSum = validAccounts.Where(a => a.LedgerCode == LedgerCode.电话收入).Sum(a => a.Balance);

                    remain.OtherFeeSum = validAccounts.Where(a => a.LedgerParentCode == LedgerCode.其他收入).Sum(a => a.Balance);

                    remain.DebitSum = validAccounts.Where(a => a.LedgerType == AccountType.D).Sum(a => a.Balance);

                    remain.PaidSum = validAccounts.Where(a => a.LedgerType == AccountType.C).Sum(a => a.Balance);
                }
            }

            #endregion

            if (yesterRemain!=null)
            {
                remain.LastRemain = yesterRemain.AllRemain;
            }

            remain.Status = accountRoot.Status;

            remain.Memo = order.Note;

            return remain;
        }

        private PriceDetail GetPriceDetail(AccountRoot accountRoot, DateTime todayBizday, string hotelId)
        {
            PriceDetail price = null;

            string ars_S = EnumHelper.GetCode(AccountStatus.S);
            

            if (accountRoot.Order != null && accountRoot.Order.PriceDetail != null && accountRoot.Order.PriceDetail.Count > 0)
            {
                DateTime day = accountRoot.Status == ars_S ? accountRoot.Order.CheckOutBizDay??todayBizday : todayBizday;

                price = accountRoot.Order.PriceDetail.FirstOrDefault(p => p.BizDay == day);
            }

            if (price == null && accountRoot.Order!=null)
            {
                IPriceDetailRepository priceDetailRepository = container.Resolve<IPriceDetailRepository>();

                string orderId = accountRoot.AccountRootID;

                DateTime date = accountRoot.Status == ars_S ? accountRoot.Order.CheckOutBizDay??todayBizday : todayBizday;

                Expression<Func<PriceDetail, bool>> priceExp = p => p.HotelID == hotelId && p.OrderID == orderId && p.BizDay == date;

                price = priceDetailRepository.Find(hotelId, priceExp);

            }

            return price;
        }

    }
}
